package com.zlyx.easy.swagger;

import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Multimaps.asMap;
import static com.google.common.collect.Sets.newTreeSet;
import static springfox.documentation.service.Tags.toTags;
import static springfox.documentation.spi.service.contexts.Orderings.listingReferencePathComparator;
import static springfox.documentation.spring.web.paths.Paths.splitCamelCase;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.zlyx.easy.core.tool.EasyBuffer;
import com.zlyx.easy.core.tool.Ops;
import com.zlyx.easy.core.utils.ApplicationUtils;
import com.zlyx.easy.core.utils.MethodUtils;
import com.zlyx.easy.swagger.annotations.SpringController;
import com.zlyx.easy.swagger.config.ApiConfiguration;

import io.swagger.annotations.Api;
import springfox.documentation.PathProvider;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.DocumentationBuilder;
import springfox.documentation.builders.ResourceListingBuilder;
import springfox.documentation.service.ApiListing;
import springfox.documentation.service.ApiListingReference;
import springfox.documentation.service.Documentation;
import springfox.documentation.service.PathAdjuster;
import springfox.documentation.service.ResourceGroup;
import springfox.documentation.service.ResourceListing;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.service.contexts.ApiSelector;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.spring.web.paths.PathMappingAdjuster;
import springfox.documentation.spring.web.scanners.ApiListingReferenceScanResult;
import springfox.documentation.spring.web.scanners.ApiListingScanner;
import springfox.documentation.spring.web.scanners.ApiListingScanningContext;

@Component
public class EasySwaggerApiDocumentationScanner {

	private ApiConfiguration apiConfiguration;

	private ApiListingScanner apiListingScanner;

	@Autowired
	public EasySwaggerApiDocumentationScanner(ApiListingScanner apiListingScanner, ApiConfiguration apiConfiguration) {
		this.apiListingScanner = apiListingScanner;
		this.apiConfiguration = apiConfiguration;
	}

	@SuppressWarnings("deprecation")
	public ApiListingReferenceScanResult scanResourceGroup(DocumentationContext context) {

		ArrayListMultimap<ResourceGroup, RequestMappingContext> resourceGroupRequestMappings = ArrayListMultimap
				.create();
		ApiSelector selector = context.getApiSelector();
		Iterable<RequestHandler> matchingHandlers = from(context.getRequestHandlers())
				.filter(selector.getRequestHandlerSelector());
		com.google.common.base.Optional<SpringController> springController = null;
		com.google.common.base.Optional<Api> api = null;
		String groupName = null;
		boolean hidden = true;
		for (RequestHandler handler : matchingHandlers) {
			springController = handler.findControllerAnnotation(SpringController.class);
			api = handler.findControllerAnnotation(Api.class);
			hidden = true;
			if (springController.isPresent()) {
				hidden = springController.get().level() < apiConfiguration.getLevel();
				groupName = Ops.toString(springController.get().todo());
			} else if (api.isPresent()) {
				hidden = api.get().hidden();
				groupName = api.get().tags()[0];
				if (Ops.isEmpty(groupName)) {
					groupName = api.get().value();
				}
				if (Ops.isEmpty(groupName)) {
					groupName = api.get().description();
				}
			} else {
				hidden = !handler.getHandlerMethod().getMethod().getDeclaringClass().getName()
						.startsWith(ApplicationUtils.getMainPackagePath());
				groupName = controllerNameAsGroup(handler.getHandlerMethod().getMethod().getDeclaringClass());
			}
			if (!hidden) {
				if (Ops.isEmpty(groupName)) {
					groupName = handler.getName();
				}
				ApiConfiguration.addTagsName(handler.getName(), EasyBuffer.newString("        【接口路径: ",
						MethodUtils.getHelpfulName(handler.getHandlerMethod().getMethod()), "】"));
				ResourceGroup resourceGroup = new ResourceGroup(groupName, handler.declaringClass(), 0);
				resourceGroupRequestMappings.put(resourceGroup, new RequestMappingContext(context, handler));
			}
		}
		return new ApiListingReferenceScanResult(asMap(resourceGroupRequestMappings));
	}

	public Documentation scan(DocumentationContext context) {
		ApiListingReferenceScanResult result = scanResourceGroup(context);
		ApiListingScanningContext listingContext = new ApiListingScanningContext(context,
				result.getResourceGroupRequestMappings());
		Multimap<String, ApiListing> apiListings = apiListingScanner.scan(listingContext);
		Set<Tag> tags = toTags(apiListings);
		tags.addAll(context.getTags());
		DocumentationBuilder group = new DocumentationBuilder().name(context.getGroupName())
				.apiListingsByResourceGroupName(apiListings).produces(context.getProduces())
				.consumes(context.getConsumes()).host(context.getHost()).schemes(context.getProtocols())
				.basePath(context.getPathProvider().getApplicationBasePath()).extensions(context.getVendorExtentions())
				.tags(tags);
		Set<ApiListingReference> apiReferenceSet = newTreeSet(listingReferencePathComparator());
		apiReferenceSet.addAll(apiListingReferences(apiListings, context));

		ResourceListing resourceListing = new ResourceListingBuilder().apiVersion(context.getApiInfo().getVersion())
				.apis(from(apiReferenceSet).toSortedList(context.getListingReferenceOrdering()))
				.securitySchemes(context.getSecuritySchemes()).info(context.getApiInfo()).build();
		group.resourceListing(resourceListing);
		return group.build();
	}

	private Collection<? extends ApiListingReference> apiListingReferences(Multimap<String, ApiListing> apiListings,
			DocumentationContext context) {
		Map<String, Collection<ApiListing>> grouped = Multimaps.asMap(apiListings);
		return FluentIterable.from(grouped.entrySet()).transform(toApiListingReference(context)).toSet();
	}

	private Function<Map.Entry<String, Collection<ApiListing>>, ApiListingReference> toApiListingReference(
			final DocumentationContext context) {
		return new Function<Map.Entry<String, Collection<ApiListing>>, ApiListingReference>() {
			@Override
			public ApiListingReference apply(Map.Entry<String, Collection<ApiListing>> input) {
				String description = Joiner.on(System.getProperty("line.separator"))
						.join(descriptions(input.getValue()));
				PathAdjuster adjuster = new PathMappingAdjuster(context);
				PathProvider pathProvider = context.getPathProvider();
				String path = pathProvider.getResourceListingPath(context.getGroupName(), input.getKey());
				return new ApiListingReference(adjuster.adjustedPath(path), description, 0);
			}
		};
	}

	public static String controllerNameAsGroup(Class<?> controllerClass) {
		return splitCamelCase(controllerClass.getSimpleName(), "-").replace("/", "").toLowerCase();
	}

	private Iterable<String> descriptions(Collection<ApiListing> apiListings) {
		return FluentIterable.from(apiListings).transform(toDescription()).toSortedList(Ordering.natural());
	}

	private Function<ApiListing, String> toDescription() {
		return new Function<ApiListing, String>() {
			@Override
			public String apply(ApiListing input) {
				return input.getDescription();
			}
		};
	}

}
